
// Reference: http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3
// Using lib: winmm.lib

#include "_WaveOutEngine.h"

#include <System/Diagnostics/Debug.h>
#include <System/Threading/Thread.h>
#include <System/String.h>

using namespace Riccsson::System;
using namespace Riccsson::System::Threading;
using namespace Riccsson::Xna::Framework::Audio;

// For native audio.
#include <Windows.h>
#include <mmsystem.h>

/*
 * some good values for block size and count
 */
#define BLOCK_SIZE 2048
#define BLOCK_COUNT 6

HWAVEOUT hWaveOut;

/*
 * module level variables
 */
static CRITICAL_SECTION waveCriticalSection;
static WAVEHDR* waveBlocks;
static volatile int waveFreeBlockCount;
static int waveCurrentBlock;

static void CALLBACK waveOutProc(
	HWAVEOUT hWaveOut,
	UINT uMsg,
	DWORD dwInstance,
	DWORD dwParam1,
	DWORD dwParam2)
{
	/*
	* pointer to free block counter
	*/
	int* freeBlockCounter = (int*)dwInstance;
	/*
	* ignore calls that occur due to openining and closing the
	* device.
	*/
	if(uMsg != WOM_DONE)
		return;
	EnterCriticalSection(&waveCriticalSection);
	(*freeBlockCounter)++;
	LeaveCriticalSection(&waveCriticalSection);
}

WAVEHDR* allocateBlocks(int size, int count)
{
	unsigned char* buffer;
	int i;
	WAVEHDR* blocks;
	DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count;
	/*
	* allocate memory for the entire set in one go
	*/
	if((buffer = (unsigned char*)HeapAlloc(
		GetProcessHeap(),
		HEAP_ZERO_MEMORY,
		totalBufferSize
		)) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		ExitProcess(1);
	}
	/*
	* and set up the pointers to each bit
	*/
	blocks = (WAVEHDR*)buffer;
	buffer += sizeof(WAVEHDR) * count;
	for(i = 0; i < count; i++)
	{
		blocks[i].dwBufferLength = size;
		blocks[i].lpData = (LPSTR)buffer;
		buffer += size;
	}
	return blocks;
}

void freeBlocks(WAVEHDR* blockArray)
{
	/*
	* and this is why allocateBlocks works the way it does
	*/
	HeapFree(GetProcessHeap(), 0, blockArray);
}

void writeAudio(HWAVEOUT hWaveOut, LPSTR data, int size)
{
	WAVEHDR* current;
	int remain;
	current = &waveBlocks[waveCurrentBlock];
	while(size > 0)
	{
		/*
		* first make sure the header we're going to use is unprepared
		*/
		if(current->dwFlags & WHDR_PREPARED)
			waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));

		if(size < (int)(BLOCK_SIZE - current->dwUser))
		{
			memcpy(current->lpData + current->dwUser, data, size);
			current->dwUser += size;
			break;
		}

		remain = BLOCK_SIZE - current->dwUser;
		memcpy(current->lpData + current->dwUser, data, remain);
		size -= remain;
		data += remain;
		current->dwBufferLength = BLOCK_SIZE;
		waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
		waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
		EnterCriticalSection(&waveCriticalSection);
		waveFreeBlockCount--;
		LeaveCriticalSection(&waveCriticalSection);

		/*
		* wait for a block to become free
		*/
		while(!waveFreeBlockCount)
			Sleep(10);

		/*
		* point to the next block
		*/
		waveCurrentBlock++;
		waveCurrentBlock %= BLOCK_COUNT;
		current = &waveBlocks[waveCurrentBlock];
		current->dwUser = 0;
	}
}

void _WaveOutEngine::runBuffer()
{
	OnBuffer(null, GC_PTR<EventArgs>(null));
}

void _WaveOutEngine::submitBuffer(byte* buffer, int length)
{
	//writeAudio(hWaveOut, (LPSTR)buffer, sizeof(buffer));
	writeAudio(hWaveOut, (LPSTR)buffer, length);
}

_WaveOutEngine::_WaveOutEngine()
	: m_running(false)
	, m_threadRunning(false)
	, m_paused(false)
{

}
void _WaveOutEngine::initialize()
{
	//HWAVEOUT hWaveOut; /* device handle */
	WAVEFORMATEX wfx; /* look this up in your documentation */
	MMRESULT result(0);/* for waveOut return values */
	/*
	* first we need to set up the WAVEFORMATEX structure.
	* the structure describes the format of the audio.
	*/
	wfx.nSamplesPerSec = 44100; /* sample rate */
	wfx.wBitsPerSample = 16; /* sample size */
	wfx.nChannels = 2; /* channels*/
	/*
	* WAVEFORMATEX also has other fields which need filling.
	* as long as the three fields above are filled this should
	* work for any PCM (pulse code modulation) format.
	*/
	wfx.cbSize = 0; /* size of _extra_ info */
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
	wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

	/*
	 * try to open the default wave device. WAVE_MAPPER is
	 * a constant defined in mmsystem.h, it always points to the
	 * default wave device on the system (some people have 2 or
	 * more sound cards).
	 */
	if(waveOutOpen(
		&hWaveOut,
		WAVE_MAPPER,
		&wfx,
		(DWORD_PTR)waveOutProc,
		(DWORD_PTR)&waveFreeBlockCount,
		CALLBACK_FUNCTION
		) != MMSYSERR_NOERROR)
	{
		//fprintf(stderr, "%s: unable to open wave mapper device\n", argv[0]);
		ExitProcess(1);
	}

	/*
	 * initialise the module variables
	 */
	waveBlocks = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT);
	waveFreeBlockCount = BLOCK_COUNT;
	waveCurrentBlock= 0;
	InitializeCriticalSection(&waveCriticalSection);
}

void _WaveOutEngine::release()
{
	/*
	* device is now open so print the success message
	* and then close the device again.
	*/
	printf("The Wave Mapper device was opened successfully!\n");
	waveOutClose(hWaveOut);
}

void _WaveOutEngine::play()
{
	if(!m_running && !m_threadRunning)
	{
		m_threadRunning = true;
		m_running = true;
		m_paused = false;

		Thread start(Threading::ThreadStart(this, &_WaveOutEngine::threadPlaying));
		start.Start();
	}
	else
	{
		m_paused = false;
	}
}

void _WaveOutEngine::stop()
{
	m_running = false;
}

void _WaveOutEngine::pause()
{
	m_paused = true;
}

Void _WaveOutEngine::threadPlaying()
{
	// Playback loop.
	while(m_running)
	{
		// If no sound source is created. Skip to fetch sound buffers.
		if(OnBuffer != null)
		{
			OnBuffer(null, null);
		}
	}

	// Wait for all blocks to complete.
	while(waveFreeBlockCount < BLOCK_COUNT)
	Sleep(10);

	// Unprepare any blocks that are still prepared.
	for(int i = 0; i < waveFreeBlockCount; i++)
	{
		if(waveBlocks[i].dwFlags & WHDR_PREPARED)
		{
			waveOutUnprepareHeader(hWaveOut, &waveBlocks[i], sizeof(WAVEHDR));
		}
	}

	DeleteCriticalSection(&waveCriticalSection);
	freeBlocks(waveBlocks);
	waveOutClose(hWaveOut);

	m_threadRunning = false;
	return Void();
}
